-- You Can't ComputerCraft, Under Pressure, by Symmetryc
local max_x, max_y = term.getSize()

-- Mah API
local function copy(_t)
	local t = {}
	for k, v in pairs(_t) do
		t[k] = type(v) == "table" and copy(v) or v
	end
	return t
end
local ibuffer = {
	buffer = function()
		return {
			act = {};
			pos_x = 1;
			pos_y = 1;
			shift_x = 0;
			shift_y = 0;
			back = colors.white;
			text = colors.lightGray;
			blink = false;
			write = function(self, _str)
				local act = self.act
				local append = true
				if self.pos_x ~= act.pos_y or self.pos_x ~= act.pos_y then
					act[#act + 1] = {term.setCursorPos, self.pos_x, self.pos_y}
					append = false
				end
				if self.back ~= act.back then
					act[#act + 1] = {term.setBackgroundColor, self.back}
					act.back = self.back
					append = false
				end
				if self.text ~= act.text then
					act[#act + 1] = {term.setTextColor, self.text}
					act.text = self.text
					append = false
				end
				for line, nl in _str:gmatch("([^\n]*)(\n?)") do
					if append then
						act[#act][2] = act[#act][2]..line
						append = false
					else
						act[#act + 1] = {term.write, line}
					end
					if nl == "\n" then
						self.pos_y = self.pos_y + 1
						act[#act + 1] = {term.setCursorPos, self.pos_x, self.pos_y}
					else
						self.pos_x = self.pos_x + #line
					end
				end
				act.pos_x, act.pos_y = self.pos_x, self.pox_y
				return self
			end;
			clear = function(self)
				self.act = {
					{term.setBackgroundColor, self.back};
					{term.clear};
				}
				return self
			end;
			draw = function(self)
				for i, v in ipairs(self.act) do
					if v[3] then
						v[1](v[2] + self.shift_x, v[3] + self.shift_y)
					else
						v[1](v[2])
					end
				end
				term.setCursorBlink(self.blink)
				return self
			end;
		}
	end;
	new = function()
		return {
			add = function(self, _buffer, _n)
				self[_n or #self + 1] = copy(_buffer)
				return self
			end;
			run = function(self)
				for k, v in pairs(self) do
					if type(v) == "table" and v.draw then
						v:draw()
					end
				end
				local r = {}
				while not r[1] do
					local event = {os.pullEvent()}
					for k, v in pairs(self) do
						if type(v) == "table" and v.fn then
							r = {v:fn(self, event)}
							if #r > 0 then
								break
							end
						end
					end
				end
				return unpack(r)
			end;
		}
	end;
}

local time = 0
term.setBackgroundColor(colors.white)
term.setTextColor(colors.lime)
local txt = "You Can't ComputerCraft, Under Pressure!"
term.setCursorPos(max_x / 2 - #txt / 2 + 1, 2)
term.clear()
term.write(txt)
term.setBackgroundColor(colors.lime)
term.setTextColor(colors.white)
for i = -1, 1 do
	term.setCursorPos(math.ceil(max_x / 4), math.floor(max_y / 2) + i)
	term.write((" "):rep(math.ceil(max_x / 2 + 1)))
end
local txt = "Begin!"
term.setCursorPos(math.ceil(max_x / 2 - #txt / 2), math.floor(max_y / 2))
term.write(txt)
while true do
	local _, _, p3, p4 = os.pullEvent("mouse_click")
	if p3 >= math.ceil(max_x / 4) and p3 <= math.ceil(max_x - max_x / 4) and p4 >= math.floor(max_y / 2) - 1 and p4 <= math.floor(max_y / 2) + 1 then
		break
	end
end

-- Challenges
local mt = {{}, {}}
local challenges = {
	{"Return whether x is even or not", {}, {5, false}, {8, true}, {-3, false}};
	{"Return the type of x", {type = type}, {{}, "table"}, {7, "number"}, {"Hello", "string"}};
	{"Return the length of x", {string = {len = string.len}}, {"Hello", 5}, {{1, 2, 3}, 3}};
	{"Return the y-axis cursor position", {term = {getCursorPos = function() return 5, 7 end}}, {nil, 7}};
	{"Return the metatable of x", {getmetatable = getmetatable}, {setmetatable({}, mt[1]), mt[1]}, setmetatable({}, mt[2]), mt[2]};
	{"Return the largest number index of x", {table = {maxn = table.maxn}}, {{1, 2, 3}, 3}, {{1, 2, nil, 3}, 4}};
	{"Return x factorial", {}, {5, 120}, {1, 1}, {8, 40320}};
	{"Return x (base 8) in base 10", {}, {10, 8}, {120, 80}, {0, 0}};
	{"Return false (global value, not 'not true')", {_G = {["false"] = 11}}, {nil, 11}};
}

-- Typing GUI
local gui = ibuffer.buffer()
gui.back, gui.text = colors.white, colors.black
gui:clear()
gui.pos_x, gui.pos_y = 2, 3
gui:write("Use Ctrl to enter your answer\nError/Bad Return: \n")
gui:write((" "):rep(max_x).."\n")
for i = 6, max_y - 1 do
	gui.pos_x, gui.pos_y = 1, i
	gui.back = colors.white
	gui:write("  ")
	gui.back = colors.lime
	gui:write((" "):rep(max_x - 4))
	gui.back = colors.white
	gui:write("  ")
end
gui:write("\n"..(" "):rep(max_x))
gui.code, gui.cur = "", 0
gui.pos_x, gui.pos_y = 4, 7
gui.text = colors.white
gui.blink = true
local m4 = max_x - 6
gui.fn = function(self, obj, event)
	if event[1] == "char" and #self.code < m4 * (max_y - 8) then
		self.code = self.code:sub(1, self.cur)..event[2]..self.code:sub(self.cur + 1)
		self.cur = self.cur + 1
	elseif event[2] == keys.backspace and self.cur > 0 then
		self.code = self.code:sub(1, self.cur - 1)..self.code:sub(self.cur + 1)
		self.cur = self.cur - 1
		term.setCursorPos(4 + #self.code % m4, 6 + math.ceil(#self.code / m4 + 0.01))
		term.write(" ")
	elseif event[2] == keys.left and self.cur > 0 or event[2] == keys.right and self.cur < #self.code then
		self.cur = self.cur + (event[2] == keys.left and -1 or 1)
	elseif event[2] == keys.enter and #self.code < m4 * (max_y - 9) then
		self.code = self.code:sub(1, self.cur)..(" "):rep(m4 - #self.code % m4)..self.code:sub(self.cur + 1)
		self.cur = self.cur + m4 - (self.cur % m4)
	elseif event[2] == keys.down or event[2] == keys.up then
		self.cur = self.cur + (event[2] == keys.down and m4 or -m4)
		self.cur = self.cur < 0 and 0 or self.cur > #self.code and #self.code or self.cur
	elseif event[2] == keys.leftCtrl or event[2] == keys.rightCtrl then
		local env, err, pass, f = obj.challenge[2]
		for i = 3, #obj.challenge do
			env.x = obj.challenge[i][1]
			f, err = loadstring(self.code)
			if not f then break end
			f, err = pcall(setfenv(f, env))
			if not f or err ~= obj.challenge[i][2] then break end
			if i == #obj.challenge then pass = true end
		end
		if not pass then
			term.setCursorPos(19, 4)
			term.setBackgroundColor(colors.white)
			term.setTextColor(colors.red)
			term.write(tostring(err)..(" "):rep(#tostring(err) < (max_x - 8) and max_x - 8 - #tostring(err) or 0))
		else
			os.pullEvent("timer")
			time = time + 1
			return pass
		end
	elseif event[1] == "timer" then
		time = time + 1
		local stime = tostring(time):match("[^%.]*")
		term.setCursorPos(max_x - 1 - #stime, 2)
		term.setTextColor(colors.lime)
		term.setBackgroundColor(colors.white)
		term.write(stime)
		os.startTimer(1)
	end
	term.setBackgroundColor(colors.lime)
	term.setTextColor(colors.white)
	for i = 1, math.ceil(#self.code / m4) do
		term.setCursorPos(4, i + 6)
		term.write(self.code:sub((i - 1) * m4 + 1 , i * m4))
	end
	term.setCursorPos(4 + self.cur % m4, 6 + math.ceil(self.cur / m4 + 0.01))
end

-- Tasks
local tasks = {math.random(1, 3), math.random(4, 6), math.random(7, 9)}

-- Program
for k, v in pairs(tasks) do
	local q = ibuffer.buffer()
	q.text, q.pos_x, q.pos_y = colors.black, 2, 2
	q:write(challenges[v][1]).blink = true
	q.text, q.pos_x, q.pos_y = colors.white, 4, 7
	q:write("")
	local t = ibuffer.new():add(gui):add(q)
	t.challenge = challenges[v]
	os.startTimer(1)
	t:run()
end
term.setBackgroundColor(colors.black)
term.setTextColor(colors.white)
term.setCursorPos(1, 1)
term.clear()
print("Your time was "..time..", congratz!")